/* eslint-disable complexity */
/// <reference path="../Rendering/rasterState.ts" />
/// <reference path="../Rendering/depthState.ts" />
/// <reference path="../Rendering/blendState.ts" />
/// <reference path="../Rendering/stencilState.ts" />
/// <reference path="./asset.ts" />

namespace FIREFLYX {
    /** 比较函数 */
    export enum CompareFunc {
        /** 从不通过. */
        NEVER = WebGL2RenderingContext.NEVER,
        /** 当新值小于旧值时通过. */
        LESS = WebGL2RenderingContext.LESS,
        /** 当值相等时通过. */
        EQUAL = WebGL2RenderingContext.EQUAL,
        /** 当新值小于或等于旧值时通过. */
        LESS_EQUAL = WebGL2RenderingContext.LEQUAL,
        /** 当新值大于旧值时通过. */
        GREATER = WebGL2RenderingContext.GREATER,
        /** 当值不同时通过. */
        NOT_EQUAL = WebGL2RenderingContext.NOTEQUAL,
        /** 当新值大于或等于旧值时通过. */
        GREATER_EQUAL = WebGL2RenderingContext.GEQUAL,
        /** 始终通过. */
        ALWAYS = WebGL2RenderingContext.ALWAYS
    }

    /** 渲染材质资源 */
    export class Material extends Asset {
        /** 光栅状态 */
        public rasterState: RasterState = RasterState.getState();
        /** 深度状态 */
        public depthState: DepthState = DepthState.getState();
        /** 混合状态 (未开启为null) */
        public blendState: Nullable<BlendState> = null;
        /** 模板状态 (未开启为null) */
        public stencilState: Nullable<StencilState> = null;
        /** 渲染队列值 */
        public renderQueue: number = 1000;
        //着色器
        private _shader: Shader;
        //属性值容器
        private _propertyMap: ShaderPropertyMap = {};
        //材质 UBO 字典
        private _uboMap: { [bName: string]: WebGLBuffer } = {};
        //属性 有变化标记字典
        private _propertyDirtyMap: Nullable<{ [name: string]: boolean }>;
        //属性 块 有变化标记字典
        private _chunkDiryMap: Nullable<{ [cName: string]: boolean }>;
        //空map
        private _emptyMap = {};
        //
        private _lightMode = "";
        //能修改
        private _lockModify: boolean = false;
        //材质自己的 关键字
        private _keyword: ShaderKeyword = ShaderKeyword.Get();

        /**
         * 获取 材质 KeywordID
         * @returns 材质 KeywordID
         */
        public getKeywordID() {
            return this._keyword.chainID;
        }

        /** 着色器资源对象 */
        public get shader() { return this._shader; }
        public set shader(val) {
            if (this._lockModify) return;
            if (this._shader == val) return;
            this._shader = val;
            //reset
            const ubokeys = Object.keys(this._uboMap);
            if (ubokeys.length > 0) {
                //清理 ubo
                const gl = App.webgl2;
                for (let i = 0, len = ubokeys.length; i < len; i++) {
                    const key = ubokeys[i];
                    const ubo = this._uboMap[key];
                    if (!ubo) continue;
                    gl.deleteBuffer(ubo);
                }
                this._uboMap = {};
            }
            this._propertyDirtyMap = null;
            this._chunkDiryMap = null;
        }

        /** 属性字典 */
        public get propertyMap() { return this._propertyMap; }

        /** 默认属性字典 ，来自于shader*/
        public get defaultPropertyMap() {
            if (!this._shader) return null;
            return this._shader.propertyMap;
        }

        /** 材质 lightMode */
        public get lightMode(): string {
            if (this._lightMode) return this._lightMode;
            return this._shader ? this._shader.lightMode : "";
        }
        public set lightMode(val) {
            if (this._lockModify) return;
            if (val == null) val = "";
            this._lightMode = val;
        }

        /** 锁定修改(材质不能被修改) */
        public get lockModify(): boolean { return this._lockModify; }
        public set lockModify(val: boolean) { this._lockModify = val; }

        /**
         * 设置 材质的属性值, 用于对应shader unifrom 全局字段
         * @param name 属性名字
         * @param value 值
         */
        public setProperty(name: string, value: UniformUserValue | UniformUserValue[]) {
            if (this._lockModify) return;

            if (value == this._propertyMap[name]) {
                if (!this._propertyDirtyMap) this._propertyDirtyMap = {};
                this._propertyDirtyMap[name] = true;
                return;
            }

            const isSuccess = ShaderUtil.SetProperty(this._propertyMap, name, value);

            if (isSuccess) {
                const pointIdx = name.indexOf(".");
                if (pointIdx != -1) {
                    const chunkN = name.substring(0, pointIdx);
                    if (!this._chunkDiryMap) this._chunkDiryMap = {};
                    this._chunkDiryMap[chunkN] = true;
                }
            }

            // return;
            // if (!name) { console.log(`setProperty invalid , property name is null.`); }
            // if (value == this._propertyMap[name]) {
            //     if (!this._propertyDirtyMap) this._propertyDirtyMap = {};
            //     this._propertyDirtyMap[name] = true;
            //     return;
            // }
            // if (value == null) {
            //     delete this._propertyMap[name];
            //     return;
            // }

            // const pointIdx = name.indexOf(".");
            // if (pointIdx != -1) {
            //     const chunkN = name.substring(0, pointIdx);
            //     if (!this._chunkDiryMap) this._chunkDiryMap = {};
            //     this._chunkDiryMap[chunkN] = true;
            // }

            // //是否是 materialValue[]?
            // if (!Array.isArray(value)) {
            //     this._propertyMap[name] = this.getUseValue(value);
            // } else {
            //     let len = value.length;
            //     let arr = this._propertyMap[name] as (BufferSource | TextureBase)[];
            //     if (arr == null) {
            //         arr = this._propertyMap[name] = [];
            //     }
            //     arr.length = len;
            //     for (let i = 0; i < len; i++) {
            //         const subVal = value[i];
            //         arr[i] = this.getUseValue(subVal);
            //     }
            // }
        }

        /**
         * 获取 材质的属性值
         * @param name 属性名字
         * @returns 值
         */
        public getProperty(name: string) {
            return this._propertyMap[name];
        }

        // tslint:disable-next-line: cyclomatic-complexity
        public uploadUniform(renderCtx: RenderContext) {
            if (!this._shader) {
                console.log(`on Material.uploadUniform() , shader is null. `);
                return;
            }
            const sc = this.shader.getCompiled(this.getKeywordID());
            if (!sc) {
                console.log(`on Material.uploadUniform() , ShaderCompiled is null. `);
                return;
            }
            const sp = sc.program;
            if (!sp) {
                console.log(`on Material.uploadUniform() , program is null.`);
                return;
            }
            //
            const gl = renderCtx.webgl2;
            const texAct = renderCtx.textrueActivator;
            const ignMtrMap = renderCtx.ignoreInMaterialMap;
            const ignMtrBMap = renderCtx.ignoreInMaterialBlockMap;
            const unifUseInfoMap = renderCtx.uniformUseInfoMap;
            //设置
            //遍历所有 正在启用的 unifrom 属性
            const pMap = this.propertyMap;
            const dpMap = this.defaultPropertyMap;
            const globalDpMap = Shader.globalPropertyMap;

            const bpFunMap = renderCtx.builtinUniformMap;  //内建变量map
            //处理 unifrom 的数据
            for (let i = 0, len = sc.unifInfoList.length; i < len; i++) {
                const uInfo = sc.unifInfoList[i];
                const uLoca = sc.unifLocationList[i];
                const isBlcok = sc.unifIndexIsBlockMap[i] == true;
                const uName = uInfo.name;
                //过滤 非材质处理 uniform
                if (isBlcok || ignMtrMap[uName]) { continue; }
                // const isArray = uInfo.size > 1;
                const unifT = unifUseInfoMap[uInfo.type];
                const isTexture = unifT == null;
                let pVal: UnifromNormalValue | UnifromNormalValue[] | null = pMap[uName];                                     //材质变量
                if (pVal == null && bpFunMap) {
                    const bFun = bpFunMap.get(uName);
                    if (bFun) pVal = bFun();                                //内建变量
                }
                if (pVal == null && dpMap) pVal = dpMap[uName];             //默认变量
                if (pVal == null && globalDpMap) pVal = globalDpMap[uName]; //全局默认变量
                if (pVal == null) {
                    //console.warn(`material "${this.name}" of uniform "${uName}" Get parameter is null.`);
                    //continue;
                    //空填默认值
                    if (isTexture) {
                        pVal = DefaultAsset.GetTexture(DefTexture.WHITE);
                    } else {
                        pVal = unifT.defVal;
                    }
                }

                if (!isTexture) {
                    const isMatrix = unifT.lenght > 4;
                    if (!isMatrix) {
                        unifT.uploadFun(uLoca, pVal);
                    } else {
                        const transpose = false;
                        unifT.uploadFun(uLoca, transpose, pVal);
                    }
                } else {
                    let tex: TextureBase = pVal as TextureBase;
                    switch (uInfo.type) {
                        case gl.SAMPLER_2D:
                        case gl.SAMPLER_CUBE:
                            const succ = texAct.activeTexOnce();
                            const texTarget = uInfo.type == gl.SAMPLER_CUBE ? gl.TEXTURE_CUBE_MAP : gl.TEXTURE_2D;
                            if (succ) {
                                gl.bindTexture(texTarget, tex.glTextrue);
                                gl.uniform1i(uLoca, texAct.currActiveTexID);
                            } else {
                                console.warn(`shader sampler Quantity exceeds limit , The maximum supported number of this device is ${texAct.maxCount}, unifrom name : ${uName}`);
                            }
                            break;
                        case gl.SAMPLER_3D: break;
                        case gl.SAMPLER_2D_SHADOW: break;
                        case gl.SAMPLER_2D_ARRAY: break;
                        case gl.SAMPLER_2D_ARRAY_SHADOW: break;
                        default:
                    }
                }
            }

            let uboBindingIndex = UBOBindingEnum.MATERIAL_START;
            const emptyMap = this._emptyMap;
            const cDrityMap = this._chunkDiryMap;
            //处理 unifrom block 的数据
            for (let i = 0, len = sc.unifBlockInfoList.length; i < len; i++) {
                const blockInfo = sc.unifBlockInfoList[i];
                const bName = blockInfo.name;
                //过滤 非材质处理 uniform
                if (ignMtrBMap[bName]) { continue; }
                let isblockDirty = cDrityMap && cDrityMap[bName];
                //block 索引于 binding index 关联
                gl.uniformBlockBinding(sp, i, uboBindingIndex);
                //获取 ubo
                let ubo = this._uboMap[bName];
                if (!ubo) {
                    //初始化 ubo
                    isblockDirty = true;
                    ubo = this._uboMap[bName] = gl.createBuffer() as WebGLBuffer;
                    gl.bindBuffer(gl.UNIFORM_BUFFER, ubo);
                    gl.bufferData(gl.UNIFORM_BUFFER, blockInfo.dSize, gl.DYNAMIC_DRAW);  //初始化 buffer 设置大小, 通过API 获取 大小
                } else {
                    gl.bindBuffer(gl.UNIFORM_BUFFER, ubo);
                }

                if (isblockDirty) {
                    //设置数据到ubo
                    renderCtx.uboDataSet(sp, i, pMap, dpMap || globalDpMap || emptyMap);
                }
                //ubo 和 binding index 关联,确定定最终block使用的数据
                gl.bindBufferRange(gl.UNIFORM_BUFFER, uboBindingIndex, ubo, 0, blockInfo.dSize);

                //索引增加 为下一个block使用
                uboBindingIndex++;
            }

            //ubo 数据更新完成,ubo bind 设为空
            gl.bindBuffer(gl.UNIFORM_BUFFER, null);

            //清理 dirty Map
            this._chunkDiryMap = null;
            this._propertyDirtyMap = null;
        }

        /**
         * 启用 关键字
         * @param keyword 关键字
         */
        public enableKeyword(keyword: string) {
            if (!keyword) return;
            this._keyword = this._keyword.enable(keyword);
        }

        /**
         * 禁用 关键字
         * @param keyword 关键字
         */
        public disableKeyword(keyword: string) {
            if (!keyword) return;
            this._keyword = this._keyword.disable(keyword);
        }

        /**
         * 关键字开关
         * @param keyword 关键字
         * @param enable 是否开启
         */
        public switchKeyword(keyword: string, enable: boolean) {
            if (enable) {
                this.enableKeyword(keyword);
            } else {
                this.disableKeyword(keyword);
            }
        }

        private getUseValue(val: UniformUserValue): BufferSource | TextureBase {
            if (typeof (val) == `object`) {
                if ((val as any).byteLength != null) {
                    return val as BufferSource;
                } else if ((val as any).asArray != null) {
                    const arr = (val as any).asArray() as (number[] | Float32Array);
                    return Array.isArray(arr) ? new Float32Array(arr) : arr;
                }

                //纹理
                return val as TextureBase;
            }

            //is number
            return new Float32Array([val]);
        }

    }
}